using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace GameFrame
{
    internal struct ComponentDataBuffer
    {
        private const int DEFAULT_BLOCK_CAPACITY = 4;
        internal ushort _flags;
        internal ushort _singletonSparse;
        internal int _count;
        internal int _pendingRemoval;
        internal int _stride;
        internal int _version;
        internal int _componentTypeID;
        internal unsafe ComponentDataBuffer.Block* _blocksList;
        internal int _blocksListCapacity;
        internal int _blocksListCount;
        internal int _blockCapacity;
        internal int _blockByteSize;
        internal int _blockDataOffset;
        internal unsafe ushort* _sparse;
        internal int _sparseCapacity;

        /// <summary>
        /// How many valid entries in the buffer are currently filled with data,
        /// either actively linked to an entity or with pending removal.
        /// Note: The first buffer entry (index 0) is always invalid and not used.
        /// </summary>
        public int Count => this._count - 1;

        /// <summary>
        /// How many valid entries in the buffer are currently filled with data and
        /// are actively linked to an existing entity, i.e. entries with pending removal are not included.
        /// </summary>
        public int UsedCount
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.Count - this._pendingRemoval;
        }

        public int Version => this._version;

        public int Stride => this._stride;

        public int BlockListCount => this._blocksListCount;

        public int BlockCapacity => this._blockCapacity;

        internal EntityRef SingletonRef
        {
            get
            {
                if (!this.IsSingleton)
                    throw new InvalidOperationException(string.Format("Not a singleton : {0}", (object) ComponentTypeId.GetComponentType(this._componentTypeID)));
                return this._singletonSparse == (ushort) 0 ? EntityRef.None : this.GetEntityRefByIndex((int) this._singletonSparse);
            }
        }

        internal bool IsSingleton
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ((int) this._flags & 1) == 1;
        }

        internal static unsafe ComponentDataBuffer Create(
            Native.Allocator allocator,
            FrameBase.EntitiesConfig config,
            Type type,
            int dataStride,
            ComponentFlags flags)
        {
            ComponentDataBuffer componentDataBuffer = new ComponentDataBuffer()
            {
                _count = 1,
                _pendingRemoval = 0,
                _stride = dataStride,
                _flags = checked ((ushort) (uint) flags),
                _singletonSparse = 0,
                _sparseCapacity = 0,
                _sparse = (ushort*) null,
                _componentTypeID = ComponentTypeId.GetComponentIndex(type),
                _blockCapacity = config.GetComponentBlockCapacity(type)
            };
            componentDataBuffer._blockDataOffset = Native.RoundUpToAlignment(sizeof (EntityRef) * componentDataBuffer._blockCapacity, 8);
            componentDataBuffer._blockByteSize = componentDataBuffer._blockDataOffset + componentDataBuffer._stride * componentDataBuffer._blockCapacity;
            componentDataBuffer._blocksListCount = 0;
            componentDataBuffer._blocksListCapacity = 4;
            componentDataBuffer._blocksList = (ComponentDataBuffer.Block*) allocator.AllocAndClear(sizeof (ComponentDataBuffer.Block) * componentDataBuffer._blocksListCapacity);
            return componentDataBuffer;
        }

        internal static unsafe void Free(Native.Allocator allocator, ComponentDataBuffer* buffer)
        {
            for (int index = 0; index < buffer->_blocksListCapacity; ++index)
            {
                ComponentDataBuffer.Block* blockPtr = buffer->_blocksList + index;
                if ((IntPtr) blockPtr->PackedHandles != IntPtr.Zero)
                    allocator.Free((void*) blockPtr->PackedHandles);
            }
            allocator.Free((void*) buffer->_blocksList);
        }

        internal static unsafe void Copy(
            Native.Allocator allocator,
            ComponentDataBuffer* to,
            ComponentDataBuffer* from)
        {
            Assert.Check(to->_componentTypeID == from->_componentTypeID);
            Assert.Check(to->_stride == from->_stride);
            Assert.Check(to->_sparseCapacity == from->_sparseCapacity);
            Assert.Check(to->_blockCapacity == from->_blockCapacity);
            Assert.Check(to->_blockByteSize == from->_blockByteSize);
            Assert.Check(to->_blockDataOffset == from->_blockDataOffset);
            if (to->_blocksListCapacity < from->_blocksListCapacity)
                to->ExpandBlockListCapacity(allocator, from->_blocksListCapacity);
            int index;
            for (index = 0; index < from->_blocksListCapacity; ++index)
            {
                ComponentDataBuffer.Block* blockPtr1 = from->_blocksList + index;
                if ((IntPtr) blockPtr1->PackedData != IntPtr.Zero)
                {
                    ComponentDataBuffer.Block* blockPtr2 = to->_blocksList + index;
                    if ((IntPtr) blockPtr2->PackedData == IntPtr.Zero)
                        *blockPtr2 = to->AllocateBlock(allocator);
                    Native.Utils.Copy((void*) blockPtr2->PackedHandles, (void*) blockPtr1->PackedHandles, to->_blockByteSize);
                }
                else
                    break;
            }
            for (; index < from->_blocksListCapacity; ++index)
                Assert.Check((IntPtr) from->_blocksList[index].PackedData == IntPtr.Zero);
            to->_flags = from->_flags;
            to->_singletonSparse = from->_singletonSparse;
            to->_count = from->_count;
            to->_pendingRemoval = from->_pendingRemoval;
            to->_version = from->_version;
            to->_blocksListCount = from->_blocksListCount;
        }

        internal unsafe void ChangeEntityCapacity(
            ushort* oldBuffer,
            int oldCapacity,
            ushort* newBuffer,
            int newCapacity)
        {
            Assert.Check((IntPtr) this._sparse != IntPtr.Zero);
            Assert.Check(this._sparseCapacity == oldCapacity);
            Native.Utils.Copy((void*) newBuffer, (void*) oldBuffer, 2 * oldCapacity);
            this._sparse = newBuffer;
            this._sparseCapacity = newCapacity;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe byte* GetBlockData(int block)
        {
            Assert.Check(block >= 0 && block < this._blocksListCount);
            return this._blocksList[block].PackedData;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe EntityRef* GetBlockEntityRefs(int block)
        {
            Assert.Check(block >= 0 && block < this._blocksListCount);
            return this._blocksList[block].PackedHandles;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe EntityRef GetEntityRefByIndex(int index)
        {
            Assert.Check(index > 0 && index < this._count);
            return this._blocksList[index / this._blockCapacity].PackedHandles[index % this._blockCapacity];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe void* GetDataPointerByIndex(int index)
        {
            Assert.Check(index > 0 && index < this._count);
            return (void*) (this._blocksList[index / this._blockCapacity].PackedData + this._stride * (index % this._blockCapacity));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe void* GetDataPointer(EntityRef entityRef)
        {
            Assert.Check(entityRef.Index < this._sparseCapacity);
            ushort index = this._sparse[entityRef.Index];
            if (index == (ushort) 0)
                throw new InvalidOperationException(string.Format("{0} does not have this component type : {1}", (object) entityRef, (object) ComponentTypeId.GetComponentType(this._componentTypeID)));
            this.AssertHandleVersion(entityRef);
            return this.GetDataPointerByIndex((int) index);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe void* GetDataPointerFastUnsafe(EntityRef entityRef)
        {
            this.AssertHandleVersion(entityRef);
            ushort num = this._sparse[entityRef.Index];
            Assert.Check(num > (ushort) 0 && (int) num < this._count);
            return (void*) (this._blocksList[(int) num / this._blockCapacity].PackedData + this._stride * ((int) num % this._blockCapacity));
        }

        internal unsafe T GetData<T>(EntityRef entityRef) where T : unmanaged, IComponent
        {
            Assert.Check(sizeof (T) == this._stride);
            return *(T*) this.GetDataPointer(entityRef);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal unsafe T* Set<T>(Native.Allocator allocator, EntityRef entityRef, T value) where T : unmanaged, IComponent
        {
            T* bufferEntry = (T*) this.GetBufferEntry(allocator, entityRef);
            *bufferEntry = value;
            return bufferEntry;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal unsafe void* Set(Native.Allocator allocator, EntityRef entityRef, void* value)
        {
            void* bufferEntry = this.GetBufferEntry(allocator, entityRef);
            if ((IntPtr) value == IntPtr.Zero)
                Native.Utils.Clear(bufferEntry, this._stride);
            else
                Native.Utils.Copy(bufferEntry, value, this._stride);
            return bufferEntry;
        }

        private unsafe void* GetBufferEntry(Native.Allocator allocator, EntityRef entityRef)
        {
            Assert.Check(entityRef.Index < this._sparseCapacity);
            ushort num1 = this._sparse[entityRef.Index];
            int index1;
            int index2;
            if (num1 == (ushort) 0)
            {
                if (this._count >= this._blockCapacity * this._blocksListCount)
                    this.UseNextBlock(allocator);
                if (this.IsSingleton)
                {
                    if (this._singletonSparse > (ushort) 0)
                        throw new InvalidOperationException(string.Format("Attempted to create more than one instance of a singleton component : {0}", (object) ComponentTypeId.GetComponentType(this._componentTypeID)));
                    this._singletonSparse = (ushort) this._count;
                }
                ushort num2 = (ushort) this._count++;
                index1 = (int) num2 / this._blockCapacity;
                index2 = (int) num2 % this._blockCapacity;
                this._blocksList[index1].PackedHandles[index2] = entityRef;
                this._sparse[entityRef.Index] = num2;
            }
            else
            {
                if (this.IsSingleton && (int) this._singletonSparse != (int) num1)
                    this._singletonSparse = this._singletonSparse <= (ushort) 0 ? num1 : throw new InvalidOperationException(string.Format("Attempted to create more than one instance of a singleton component : {0}", (object) ComponentTypeId.GetComponentType(this._componentTypeID)));
                this.AssertHandleVersion(entityRef);
                index1 = (int) num1 / this._blockCapacity;
                index2 = (int) num1 % this._blockCapacity;
            }
            return (void*) (this._blocksList[index1].PackedData + this._stride * index2);
        }

        internal unsafe void Remove(EntityRef entityRef)
        {
            Assert.Check(entityRef.Index < this._sparseCapacity);
            ushort num = this._sparse[entityRef.Index];
            if (num == (ushort) 0)
                throw new InvalidOperationException(string.Format("Can not remove, entity {0} does not have component type : {1}", (object) entityRef, (object) ComponentTypeId.GetComponentType(this._componentTypeID)));
            this.AssertHandleVersion(entityRef);
            Assert.Check(!this.IsSingleton || (int) this._singletonSparse != (int) num);
            Assert.Check<int>(this._pendingRemoval > 0, this._pendingRemoval);
            --this._pendingRemoval;
            if ((int) num < --this._count)
            {
                int index1 = (int) num / this._blockCapacity;
                int index2 = (int) num % this._blockCapacity;
                int index3 = this._count / this._blockCapacity;
                int index4 = this._count % this._blockCapacity;
                EntityRef entityRef1 = this._blocksList[index1].PackedHandles[index2] = this._blocksList[index3].PackedHandles[index4];
                Native.Utility.CopyFast((void*) (this._blocksList[index1].PackedData + this._stride * index2), (void*) (this._blocksList[index3].PackedData + this._stride * index4), this._stride);
                this._sparse[entityRef1.Index] = num;
                this._blocksList[index3].PackedHandles[index4] = new EntityRef();
                if (this.IsSingleton && (int) this._singletonSparse == this._count)
                    this._singletonSparse = num;
            }
            else
            {
                int index5 = (int) num / this._blockCapacity;
                int index6 = (int) num % this._blockCapacity;
                this._blocksList[index5].PackedHandles[index6] = new EntityRef();
                Native.Utility.ClearFast((void*) (this._blocksList[index5].PackedData + this._stride * index6), this._stride);
            }
            ++this._version;
            this._sparse[entityRef.Index] = (ushort) 0;
        }

        private unsafe void ExpandBlockListCapacity(Native.Allocator allocator, int newCapacity)
        {
            if (newCapacity <= this._blocksListCapacity)
                return;
            this._blocksList = (ComponentDataBuffer.Block*) allocator.Expand((void*) this._blocksList, sizeof (ComponentDataBuffer.Block) * this._blocksListCapacity, sizeof (ComponentDataBuffer.Block) * newCapacity);
            this._blocksListCapacity = newCapacity;
        }

        private unsafe void UseNextBlock(Native.Allocator allocator)
        {
            if (this._blocksListCount == this._blocksListCapacity)
                this.ExpandBlockListCapacity(allocator, this._blocksListCapacity * 2);
            int index = this._blocksListCount++;
            if ((IntPtr) this._blocksList[index].PackedHandles == IntPtr.Zero)
                this._blocksList[index] = this.AllocateBlock(allocator);
            else
                Native.Utils.Clear((void*) this._blocksList[index].PackedHandles, this._blockByteSize);
        }

        internal unsafe ComponentDataBuffer.Block AllocateBlock(Native.Allocator allocator)
        {
            byte* numPtr = (byte*) allocator.AllocAndClear(this._blockByteSize, 8);
            return new ComponentDataBuffer.Block()
            {
                PackedHandles = (EntityRef*) numPtr,
                PackedData = numPtr + this._blockDataOffset
            };
        }

        [Conditional("DEBUG")]
        internal unsafe void AssertHandleVersion(EntityRef entityRef)
        {
            ushort num = this._sparse[entityRef.Index];
            Assert.Check((long) this._blocksList[(int) num / this._blockCapacity].PackedHandles[(int) num % this._blockCapacity].Raw == (long) entityRef.Raw);
        }

        internal unsafe bool HandleVersionMatches(EntityRef entityRef)
        {
            ushort num = this._sparse[entityRef.Index];
            return (long) this._blocksList[(int) num / this._blockCapacity].PackedHandles[(int) num % this._blockCapacity].Raw == (long) entityRef.Raw;
        }

        internal unsafe void RemovalPending(EntityRef entityRef)
        {
            ++this._pendingRemoval;
            if (!this.IsSingleton)
                return;
            Assert.Check(this._singletonSparse > (ushort) 0);
            Assert.Check((int) this._sparse[entityRef.Index] == (int) this._singletonSparse);
            this._singletonSparse = (ushort) 0;
        }

        internal struct Block
        {
            public unsafe EntityRef* PackedHandles;
            public unsafe byte* PackedData;
        }
    }
}